А также..
Если в ячейке написана фраза "Вывод"/"Ответ на вопрос" итд, то ожидается ответ в виде текста (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если в ячейке написано "Your code here", то ожидается ответ в виде кода (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если есть и ячейка с фразой "Вывод", и ячейка с фразой "Your code here", то в ответе ожидается и код, и текст)
Цель данного задания:
Сначала установим нужные нам версии библиотек. Мы гарантируем, что в данных версиях задание будет корректно отрабатывать.
После установки нужных версий нажмите на кнопку Restart runtime, которая появится в следующей ячейке после завершения ее выполнения.
После перезапуска (restart runtime) устанавливать библиотеки еще раз не нужно, достаточно проверить, что установилась их новая версия.
На скачивание файла и установку понадобится не более 5 минут.
Важно! Устанавливать нужные версии нужно каждый раз, когда создается новый рантайм. Например, если вы 2 часа подряд делаете это задание, то подготовить библиотеки достаточно 1 раз. Но если вы, например, начали в понедельник, затем закрыли/выключили ноутбук, то при продолжении в среду, вам нужно будет запустить рантайм заново и следовательно заново установить библиотеки.
Важно! Если вы предпочитаете делать практические задания на своем личном ноутбуке, то предлагаем вам завести виртуальное окружение и установить в нем библиотеки из файла requirements.txt по ссылке https://drive.google.com/file/d/1br9tHmdccGVzpk0svXNmHxVMh_OF2oZR/view?usp=sharing В таком случае виртуальное окружение достаточно настроить всего один раз.
! gdown 1br9tHmdccGVzpk0svXNmHxVMh_OF2oZR
! pip install -r /content/requirements.txt
Нажмите на кнопку Restart runtime, которая появилась в выводе ячейки сверху после завершения ее выполнения.
После перезапуска (restart runtime) устанавливать библиотеки еще раз не нужно, достаточно проверить, что установилась их новая версия.
import matplotlib
assert(matplotlib.__version__ == '3.5.3')
Теперь можно приступать к выполнению задания! :)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from copy import copy
Если Вы дальтоник, то можете воспользоваться готовой colormap из matplotlib (или найти свою):
plt.style.use('tableau-colorblind10')
from matplotlib.colors import ListedColormap
from sklearn.svm import LinearSVC, SVC
from sklearn.decomposition import PCA
from sklearn.model_selection import cross_val_score, cross_val_predict, cross_validate, train_test_split
from sklearn.metrics import make_scorer, accuracy_score, roc_auc_score
from sklearn.datasets import make_blobs, make_circles, make_moons
def make_moons_cls(size=1000, d=2):
X, y = make_moons(n_samples=size, noise=0.15)
if d > 2:
X = np.concatenate((X, np.random.normal(size=(size, d-2))), axis=1)
return X, y
def make_circles_cls():
X, y = make_circles()
def generate_data_with_imb_classes(size1=100, size2=10):
X = np.r_[(
np.random.normal(loc=1.0, size=(size1, 2)),
np.random.normal(loc=0.5, size=(size2, 2))
)]
y = np.ones(len(X))
y[-size2:] = 0
return X, y
def plot_separating_surface(X, y, cls, view_support=False, title=''):
x_min = min(X[:, 0]) - 0.1
x_max = max(X[:, 0]) + 0.1
y_min = min(X[:, 1]) - 0.1
y_max = max(X[:, 1]) + 0.1
h = 0.005
cm = plt.cm.RdBu
cm_bright = ListedColormap(['#FF0000', '#0000FF'])
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = cls.predict(np.c_[xx.ravel(), yy.ravel()])
plt.figure(figsize=(10, 10))
if title:
plt.title(title)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', s=40, cmap=cm_bright)
if view_support:
plt.scatter(X[cls.support_, 0], X[cls.support_, 1],
c=y[cls.support_], edgecolors='k', s=150, cmap=cm_bright)
Z = Z.reshape(xx.shape)
plt.xticks(())
plt.yticks(())
plt.contourf(xx, yy, Z, cmap=cm, alpha=.3)
plt.show()
import gdown
gdown.download_folder('https://drive.google.com/drive/folders/1bp39_Jj0edo1lzxZ3DIoChsOVy5DVi1R?usp=sharing')
В ячейке ниже генерируется выборка, состоящая из объектов двух классов. Каждый объект представлен двумя координатами, так что объекты этой выборки можно отобразить на плоскости, используя функцию scatter из библиотеки matplotlib.
В этом задании вам надо будет обучить линейную разделяющую поверхность с помощью $\mbox{sklearn.svm.SVC(kernel='linear')}$, а также нелинейную c rbf-ядром с помощью $\mbox{sklearn.svm.SVC(kernel='rbf')}$. Остальные параметры методов можете оставить дефолтными. Делить выборку на обучение и валидацию сейчас не требуется, так как нас будет пока интересовать только форма разделяющей кривой.
X, y = make_moons_cls()
linear_svc = SVC(kernel='linear')
nonlinear_svc = SVC(kernel='rbf')
Визуализируем выборку
plt.figure(figsize=(8, 6))
plt.scatter(X[y == 0, 0], X[y == 0, 1], label='y=0')
plt.scatter(X[y == 1, 0], X[y == 1, 1], label='y=1')
plt.xlabel('$x_0$')
plt.ylabel('$x_1$')
plt.legend()
plt.grid()
Обучите модели и визуализируйте разделяющую поверхность для обеих моделей с помощью функции plot_separating_surface(). Посчитайте точность (accuracy) на обучающей выборке для каждой из моделей.
linear_svc.fit(X, y)
nonlinear_svc.fit(X, y)
plot_separating_surface(X, y, linear_svc, view_support=False, title='')
plot_separating_surface(X, y, nonlinear_svc, view_support=False, title='')
print(f'Train Accuracy (linear): {accuracy_score(y, linear_svc.predict(X)):.3f}')
print(f'Train Accuracy (non linear): {accuracy_score(y, nonlinear_svc.predict(X)):.3f}')
Train Accuracy (linear): 0.872 Train Accuracy (non linear): 0.990
poly_svc = SVC(kernel='poly')
poly_svc.fit(X, y)
plot_separating_surface(X, y, poly_svc, view_support=False, title='')
print(f'Train Accuracy (linear): {accuracy_score(y, poly_svc.predict(X)):.3f}')
Train Accuracy (linear): 0.925
Сделайте вывод о получившихся результатах. Какая из моделей лучше подходит для данной выборки и почему?
разделяющаяся поверхность интуитивно нелинейная - по ощущениям полиномом апроксимируется, но все же лучше rbf(из-за большой кривизны разделяющей поверхности) - что и показывает точность моделей
Продолжаем работать с выборкой и моделями из первой части. Для линейной и rbf-моделей рассмотрим опорные объекты, полученные после обучения. Визуализировать их можно, используя функуцию plot_separating_surface с параметром vissupport=True. Достанем опорные объекты из обученной модели с помощью поля model.support.
plot_separating_surface(X, y, nonlinear_svc, view_support=True)
Пункт 1 Обучим новые две модели $\mbox{SVC(kernel='rbf')}$, используя только опорные объекты построенные с помощью соответственно линейной (linear_svc) и нелинейной (nonlinear_svc) моделей из первой части.
# Учим модели только на опорных объектах
svc_on_linear_support = SVC(kernel='rbf').fit(X[linear_svc.support_, :], y[linear_svc.support_])
svc_on_rbf_support = SVC(kernel='rbf').fit(X[nonlinear_svc.support_, :], y[nonlinear_svc.support_])
plot_separating_surface(X, y, svc_on_linear_support)
plot_separating_surface(X, y, svc_on_rbf_support)
plot_separating_surface(X, y, linear_svc, view_support=True)
Задание: Сравните полученные разделяющие поверхности с нелинейной моделью (nonlinear_svc) из первой части. Какая из поверхностей больше похожа на нелинейнную модель из первой части и почему, опишите в выводе.
Если дополнительно визулизировать опорные объекты линейной модели, то они больше разделены на полосы. Сам ответ: вторая поверхность больше похожа на нелинейную. В первом случае модель пытается разделить объекты прямой(и получаются полосы), что визуально не очень подходит
Пункт 2 Обучим модель $\mbox{SVC(kernel='rbf')}$, используя все объекты кроме тех, что являлись опорными для нелинейной модели из первой части (nonlinear_svc) и сравним эту модель вместе с svc_on_rbf_support с нелинейной моделью из первой части (nonlinear_svc). Визуализируйте разделяющие поверхности обеих моделей.
non_support_vectors = [i for i in range(len(X)) if i not in nonlinear_svc.support_]
svc_all_without_rbf_support = SVC(kernel='rbf').fit(X[non_support_vectors, :], y[non_support_vectors])
plot_separating_surface(X, y, svc_all_without_rbf_support)
plot_separating_surface(X, y, nonlinear_svc)
plot_separating_surface(X, y, svc_on_rbf_support)
Сделайте вывод: Сильно ли полученные поверхности отличаются от той, что была получена в первой части? Что произошло с пограничными объектами? Объясните полученные результаты.
Если вспомнить какими в первый раз были опорные объекты, то:
- в первом граница немного изменилась по ощущение модель забыла о некоторых объектах и теперь они пересекаются границей
- во втором наименьшее чилсо объектов на границе(аппроксимация на лицо)
- в третьем случае модель стремиться именно их(опорные) отодвинуть от границы Итого: граница зависит от того. какие объекты штрафуются за приближение к границе, что и видно
Формы разделяющих поверхностей могут быть вариативными для нелинейного случая. Иногда, выбранная форма поверхности может плохо подходить для целевого распределения объектов. Особенно это может быть заметно, если соотношение классов в обучении отличается от тестового. Такое свойственно медицинским данным, где в обучающих данных часто наблюдается перекос в сторону больных, так как именно их данные чаще собираются.
Давайте обучим SVC на несбалансированных данных и построим разделяющую поверхность для тестовой выборки с другим соотношением классов.
X_distr1 = np.load('05-SVM/imbalanced/X_imb.npz.npy')
y_distr1 = np.load('05-SVM/imbalanced/y_imb.npz.npy')
X_distr2 = np.load('05-SVM/imbalanced/X_imb_test.npz.npy')
y_distr2 = np.load('05-SVM/imbalanced/y_imb_test.npz.npy')
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) <ipython-input-20-4d68e4823208> in <module> ----> 1 X_distr1 = np.load('05-SVM/imbalanced/X_imb.npz.npy') 2 y_distr1 = np.load('05-SVM/imbalanced/y_imb.npz.npy') 3 X_distr2 = np.load('05-SVM/imbalanced/X_imb_test.npz.npy') 4 y_distr2 = np.load('05-SVM/imbalanced/y_imb_test.npz.npy') /usr/local/lib/python3.8/dist-packages/numpy/lib/npyio.py in load(file, mmap_mode, allow_pickle, fix_imports, encoding) 415 own_fid = False 416 else: --> 417 fid = stack.enter_context(open(os_fspath(file), "rb")) 418 own_fid = True 419 FileNotFoundError: [Errno 2] No such file or directory: '05-SVM/imbalanced/X_imb.npz.npy'
# Модель с дефолтными параметрами, которую Вам предстоит улучшить
base_model = SVC()
base_model.fit(X_distr1, y_distr1)
plot_separating_surface(X_distr2, y_distr2, base_model)
Так как синих объектов было существенно больше в обучении, разделяющая поверхность отнесла к этому классу большую часть пространства около границы классов. При этом, из-за возросшего количества красных объектов в тестовой выборке, многие из них стали ошибочно относиться к другому классу.
Один из способов исправить эту проблему - это повлиять на форму поверхности с помощью задания весов классов, которые задаются через параметр class_weight в sklearn.svm.SVC. Особенно это может быть полезно в задачах, где известно, что распределение классов в обучающей выборке отличается от реального.
В данном задании вам будет дана выборка с несбалансированными данными. Кроме того, дана вторая выборка, в которой классы имеют то же распределение, но классы имеют другое соотношение. Вам нужно построить различные rbf-модели, меняя параметры весов классов и визуализировать разделяющие поверхности. Попробуйте улучшить качество на второй выборке (X_distr2, y_distr2), обучаясь только на первой (X_distr1, y_distr1) меняя параметры весов классов относительно дефолтных: class_weight={1: 1.0, 0: 1.0}. В качестве метрики, которую нужно оптимизировать нужно использовать accuracy на (X_distr2, y_dist2).
from itertools import product
w1_best, w2_best = None, None
best_acc = None
grid = [0.01, 0.1, 0.5, 1.0, 5, 10, 100, 1000]
for w1, w2 in product(grid, grid):
svc_cls = SVC(C=1.0, class_weight={1: w1, 0: w2})
svc_cls.fit(X_distr1, y_distr1)
acc = accuracy_score(y_distr2, svc_cls.predict(X_distr2))
# Save model with best accuracy
# Your code here
if best_acc is None or best_acc < acc:
best_acc = acc
w1_best = w1
w2_best = w2
print(f'Weight1: {w1_best},\nWeight2: {w2_best},\nBest accuracy: {best_acc}')
Weight1: 0.1, Weight2: 5, Best accuracy: 0.9666666666666667
# best_svc_cls = ...
# Your code here
best_svc_cls = SVC(C=1.0, class_weight={1: w1_best, 0: w2_best})
best_svc_cls.fit(X_distr1, y_distr1)
plot_separating_surface(X_distr2, y_distr2, best_svc_cls)
print('My best classifier:')
print('Accuracy:', accuracy_score(y_distr2, best_svc_cls.predict(X_distr2)))
My best classifier: Accuracy: 0.9666666666666667
Возможность строить нелинейные поверхности может сильно улучшить качество, но и несет риск переобучения. В этом задании предстоит обучить лучшую svm модель и получить хорошее качество на тесте в системе тестирования. Для контроля переобучения рекомендуется пользоваться кросс-валидацией. Для улучшения качества рекомендуется подбирать
Также не забывайте, что при решении задач машинного обучения полезно смотреть в данные :)
Все csv-таблицы с данными вы можете взять из публичного теста, который также есть в проверяющей системе. Для этого распакуйте архив с публичными тестами и положите файлы в рабочей директории (рядом с ноутбуком)
X_train = np.load('05-SVM/public/cX_train.npy')
y_train = np.load('05-SVM/public/cy_train.npy')
X_test = np.load('05-SVM/public/cX_test.npy')
X_train.shape, y_train.shape, X_test.shape
((800, 5), (800,), (200, 5))
X_train
array([[ 0.11671859, -0.28403176, -0.78284937, -0.33424969, 0.73058647],
[ 2.40217514, -0.85417342, -0.52933946, 0.90435846, -0.02227596],
[-0.72708724, 0.30822518, 1.06541911, -0.54679105, -0.9495162 ],
...,
[-0.02657785, 0.02662441, 1.90038116, 0.77197724, -1.60216329],
[ 0.70314171, -2.14220338, 2.27019464, 0.50768295, -0.25348165],
[-0.19414599, 1.24983936, -0.8672368 , 0.94371956, 0.83962266]])
X = X_train
y = y_train.ravel()
from sklearn.model_selection import GridSearchCV, train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8, random_state = 42)
y_train = y_train.ravel()
y_test = y_test.ravel()
param_grid = {'kernel': ['rbf'], 'gamma': np.logspace(-4, 1, 30)}
best_model = GridSearchCV(SVC(random_state = 0), param_grid, cv = 5, n_jobs = 3,verbose = 0, scoring = 'accuracy', return_train_score = False )
best_model.fit(X_train, y_train)
print(best_model.best_estimator_)
print("Accuracy:", accuracy_score(y_test, best_model.predict(X_test)))
SVC(gamma=0.18873918221350977, random_state=0) Accuracy: 0.89375
best_model = SVC(C=1487.3521072935118, gamma=0.007880462815669913, kernel='rbf', random_state=0)
best_model.fit(X_train, y_train)
print("Accuracy:", accuracy_score(y_test, best_model.predict(X_test)))
Accuracy: 0.8875
plt.scatter(X[y == 0, 4], X[y == 0, 3], label='y=0')
plt.scatter(X[y == 1, 4], X[y == 1, 3], label='y=1')
plt.legend()
<matplotlib.legend.Legend at 0x7f87af997910>
plt.scatter(X[y == 0, 0], X[y == 0, 4], label='y=0')
plt.scatter(X[y == 1, 0], X[y == 1, 4], label='y=1')
plt.legend()
<matplotlib.legend.Legend at 0x7f87b3ee2e50>
Отправьте код обучения модели с оптимальными параметрами в проверяющую систему, воспользовавшись приложенным шаблоном svm_solution.py. Кросс-валидацию параметров в посылаемом решении делать не нужно -- достаточно подобрать, например, их тут, а в решении уже обучать модель с оптимальными параметрами.
В предыдущей части Вы обучили хорошую SVM модель, подбирая гиперпараметры модели. Давайте теперь попробуем обучить логистическую регрессию на этой же выборке, и по кросс-валидации оценить влияние гиперпараметров на линейную модель.
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 0.8, random_state = 1)
y_train = y_train.ravel()
y_test = y_test.ravel()
param_grid = {'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'], 'C': np.logspace(-10, 5, 50)}
best_model = GridSearchCV(LogisticRegression(random_state = 0), param_grid, cv = 5, n_jobs = -1,verbose = 0, return_train_score = False )
best_model.fit(X_train, y_train)
print(best_model.best_estimator_)
print("Accuracy:", accuracy_score(y_test, best_model.predict(X_test)))
best_model.cv_results_['params']
[{'C': 1e-10, 'solver': 'newton-cg'},
{'C': 1e-10, 'solver': 'lbfgs'},
{'C': 1e-10, 'solver': 'liblinear'},
{'C': 1e-10, 'solver': 'sag'},
{'C': 1e-10, 'solver': 'saga'},
{'C': 2.0235896477251556e-10, 'solver': 'newton-cg'},
{'C': 2.0235896477251556e-10, 'solver': 'lbfgs'},
{'C': 2.0235896477251556e-10, 'solver': 'liblinear'},
{'C': 2.0235896477251556e-10, 'solver': 'sag'},
{'C': 2.0235896477251556e-10, 'solver': 'saga'},
{'C': 4.0949150623804193e-10, 'solver': 'newton-cg'},
{'C': 4.0949150623804193e-10, 'solver': 'lbfgs'},
{'C': 4.0949150623804193e-10, 'solver': 'liblinear'},
{'C': 4.0949150623804193e-10, 'solver': 'sag'},
{'C': 4.0949150623804193e-10, 'solver': 'saga'},
{'C': 8.28642772854686e-10, 'solver': 'newton-cg'},
{'C': 8.28642772854686e-10, 'solver': 'lbfgs'},
{'C': 8.28642772854686e-10, 'solver': 'liblinear'},
{'C': 8.28642772854686e-10, 'solver': 'sag'},
{'C': 8.28642772854686e-10, 'solver': 'saga'},
{'C': 1.67683293681101e-09, 'solver': 'newton-cg'},
{'C': 1.67683293681101e-09, 'solver': 'lbfgs'},
{'C': 1.67683293681101e-09, 'solver': 'liblinear'},
{'C': 1.67683293681101e-09, 'solver': 'sag'},
{'C': 1.67683293681101e-09, 'solver': 'saga'},
{'C': 3.3932217718953295e-09, 'solver': 'newton-cg'},
{'C': 3.3932217718953295e-09, 'solver': 'lbfgs'},
{'C': 3.3932217718953295e-09, 'solver': 'liblinear'},
{'C': 3.3932217718953295e-09, 'solver': 'sag'},
{'C': 3.3932217718953295e-09, 'solver': 'saga'},
{'C': 6.866488450042998e-09, 'solver': 'newton-cg'},
{'C': 6.866488450042998e-09, 'solver': 'lbfgs'},
{'C': 6.866488450042998e-09, 'solver': 'liblinear'},
{'C': 6.866488450042998e-09, 'solver': 'sag'},
{'C': 6.866488450042998e-09, 'solver': 'saga'},
{'C': 1.389495494373136e-08, 'solver': 'newton-cg'},
{'C': 1.389495494373136e-08, 'solver': 'lbfgs'},
{'C': 1.389495494373136e-08, 'solver': 'liblinear'},
{'C': 1.389495494373136e-08, 'solver': 'sag'},
{'C': 1.389495494373136e-08, 'solver': 'saga'},
{'C': 2.8117686979742307e-08, 'solver': 'newton-cg'},
{'C': 2.8117686979742307e-08, 'solver': 'lbfgs'},
{'C': 2.8117686979742307e-08, 'solver': 'liblinear'},
{'C': 2.8117686979742307e-08, 'solver': 'sag'},
{'C': 2.8117686979742307e-08, 'solver': 'saga'},
{'C': 5.689866029018305e-08, 'solver': 'newton-cg'},
{'C': 5.689866029018305e-08, 'solver': 'lbfgs'},
{'C': 5.689866029018305e-08, 'solver': 'liblinear'},
{'C': 5.689866029018305e-08, 'solver': 'sag'},
{'C': 5.689866029018305e-08, 'solver': 'saga'},
{'C': 1.1513953993264481e-07, 'solver': 'newton-cg'},
{'C': 1.1513953993264481e-07, 'solver': 'lbfgs'},
{'C': 1.1513953993264481e-07, 'solver': 'liblinear'},
{'C': 1.1513953993264481e-07, 'solver': 'sag'},
{'C': 1.1513953993264481e-07, 'solver': 'saga'},
{'C': 2.329951810515372e-07, 'solver': 'newton-cg'},
{'C': 2.329951810515372e-07, 'solver': 'lbfgs'},
{'C': 2.329951810515372e-07, 'solver': 'liblinear'},
{'C': 2.329951810515372e-07, 'solver': 'sag'},
{'C': 2.329951810515372e-07, 'solver': 'saga'},
{'C': 4.7148663634573897e-07, 'solver': 'newton-cg'},
{'C': 4.7148663634573897e-07, 'solver': 'lbfgs'},
{'C': 4.7148663634573897e-07, 'solver': 'liblinear'},
{'C': 4.7148663634573897e-07, 'solver': 'sag'},
{'C': 4.7148663634573897e-07, 'solver': 'saga'},
{'C': 9.540954763499944e-07, 'solver': 'newton-cg'},
{'C': 9.540954763499944e-07, 'solver': 'lbfgs'},
{'C': 9.540954763499944e-07, 'solver': 'liblinear'},
{'C': 9.540954763499944e-07, 'solver': 'sag'},
{'C': 9.540954763499944e-07, 'solver': 'saga'},
{'C': 1.9306977288832498e-06, 'solver': 'newton-cg'},
{'C': 1.9306977288832498e-06, 'solver': 'lbfgs'},
{'C': 1.9306977288832498e-06, 'solver': 'liblinear'},
{'C': 1.9306977288832498e-06, 'solver': 'sag'},
{'C': 1.9306977288832498e-06, 'solver': 'saga'},
{'C': 3.906939937054621e-06, 'solver': 'newton-cg'},
{'C': 3.906939937054621e-06, 'solver': 'lbfgs'},
{'C': 3.906939937054621e-06, 'solver': 'liblinear'},
{'C': 3.906939937054621e-06, 'solver': 'sag'},
{'C': 3.906939937054621e-06, 'solver': 'saga'},
{'C': 7.906043210907702e-06, 'solver': 'newton-cg'},
{'C': 7.906043210907702e-06, 'solver': 'lbfgs'},
{'C': 7.906043210907702e-06, 'solver': 'liblinear'},
{'C': 7.906043210907702e-06, 'solver': 'sag'},
{'C': 7.906043210907702e-06, 'solver': 'saga'},
{'C': 1.5998587196060572e-05, 'solver': 'newton-cg'},
{'C': 1.5998587196060572e-05, 'solver': 'lbfgs'},
{'C': 1.5998587196060572e-05, 'solver': 'liblinear'},
{'C': 1.5998587196060572e-05, 'solver': 'sag'},
{'C': 1.5998587196060572e-05, 'solver': 'saga'},
{'C': 3.237457542817647e-05, 'solver': 'newton-cg'},
{'C': 3.237457542817647e-05, 'solver': 'lbfgs'},
{'C': 3.237457542817647e-05, 'solver': 'liblinear'},
{'C': 3.237457542817647e-05, 'solver': 'sag'},
{'C': 3.237457542817647e-05, 'solver': 'saga'},
{'C': 6.55128556859551e-05, 'solver': 'newton-cg'},
{'C': 6.55128556859551e-05, 'solver': 'lbfgs'},
{'C': 6.55128556859551e-05, 'solver': 'liblinear'},
{'C': 6.55128556859551e-05, 'solver': 'sag'},
{'C': 6.55128556859551e-05, 'solver': 'saga'},
{'C': 0.0001325711365590111, 'solver': 'newton-cg'},
{'C': 0.0001325711365590111, 'solver': 'lbfgs'},
{'C': 0.0001325711365590111, 'solver': 'liblinear'},
{'C': 0.0001325711365590111, 'solver': 'sag'},
{'C': 0.0001325711365590111, 'solver': 'saga'},
{'C': 0.0002682695795279727, 'solver': 'newton-cg'},
{'C': 0.0002682695795279727, 'solver': 'lbfgs'},
{'C': 0.0002682695795279727, 'solver': 'liblinear'},
{'C': 0.0002682695795279727, 'solver': 'sag'},
{'C': 0.0002682695795279727, 'solver': 'saga'},
{'C': 0.0005428675439323859, 'solver': 'newton-cg'},
{'C': 0.0005428675439323859, 'solver': 'lbfgs'},
{'C': 0.0005428675439323859, 'solver': 'liblinear'},
{'C': 0.0005428675439323859, 'solver': 'sag'},
{'C': 0.0005428675439323859, 'solver': 'saga'},
{'C': 0.0010985411419875595, 'solver': 'newton-cg'},
{'C': 0.0010985411419875595, 'solver': 'lbfgs'},
{'C': 0.0010985411419875595, 'solver': 'liblinear'},
{'C': 0.0010985411419875595, 'solver': 'sag'},
{'C': 0.0010985411419875595, 'solver': 'saga'},
{'C': 0.0022229964825261957, 'solver': 'newton-cg'},
{'C': 0.0022229964825261957, 'solver': 'lbfgs'},
{'C': 0.0022229964825261957, 'solver': 'liblinear'},
{'C': 0.0022229964825261957, 'solver': 'sag'},
{'C': 0.0022229964825261957, 'solver': 'saga'},
{'C': 0.004498432668969444, 'solver': 'newton-cg'},
{'C': 0.004498432668969444, 'solver': 'lbfgs'},
{'C': 0.004498432668969444, 'solver': 'liblinear'},
{'C': 0.004498432668969444, 'solver': 'sag'},
{'C': 0.004498432668969444, 'solver': 'saga'},
{'C': 0.009102981779915227, 'solver': 'newton-cg'},
{'C': 0.009102981779915227, 'solver': 'lbfgs'},
{'C': 0.009102981779915227, 'solver': 'liblinear'},
{'C': 0.009102981779915227, 'solver': 'sag'},
{'C': 0.009102981779915227, 'solver': 'saga'},
{'C': 0.018420699693267165, 'solver': 'newton-cg'},
{'C': 0.018420699693267165, 'solver': 'lbfgs'},
{'C': 0.018420699693267165, 'solver': 'liblinear'},
{'C': 0.018420699693267165, 'solver': 'sag'},
{'C': 0.018420699693267165, 'solver': 'saga'},
{'C': 0.03727593720314938, 'solver': 'newton-cg'},
{'C': 0.03727593720314938, 'solver': 'lbfgs'},
{'C': 0.03727593720314938, 'solver': 'liblinear'},
{'C': 0.03727593720314938, 'solver': 'sag'},
{'C': 0.03727593720314938, 'solver': 'saga'},
{'C': 0.07543120063354607, 'solver': 'newton-cg'},
{'C': 0.07543120063354607, 'solver': 'lbfgs'},
{'C': 0.07543120063354607, 'solver': 'liblinear'},
{'C': 0.07543120063354607, 'solver': 'sag'},
{'C': 0.07543120063354607, 'solver': 'saga'},
{'C': 0.15264179671752365, 'solver': 'newton-cg'},
{'C': 0.15264179671752365, 'solver': 'lbfgs'},
{'C': 0.15264179671752365, 'solver': 'liblinear'},
{'C': 0.15264179671752365, 'solver': 'sag'},
{'C': 0.15264179671752365, 'solver': 'saga'},
{'C': 0.30888435964774846, 'solver': 'newton-cg'},
{'C': 0.30888435964774846, 'solver': 'lbfgs'},
{'C': 0.30888435964774846, 'solver': 'liblinear'},
{'C': 0.30888435964774846, 'solver': 'sag'},
{'C': 0.30888435964774846, 'solver': 'saga'},
{'C': 0.6250551925273976, 'solver': 'newton-cg'},
{'C': 0.6250551925273976, 'solver': 'lbfgs'},
{'C': 0.6250551925273976, 'solver': 'liblinear'},
{'C': 0.6250551925273976, 'solver': 'sag'},
{'C': 0.6250551925273976, 'solver': 'saga'},
{'C': 1.2648552168552958, 'solver': 'newton-cg'},
{'C': 1.2648552168552958, 'solver': 'lbfgs'},
{'C': 1.2648552168552958, 'solver': 'liblinear'},
{'C': 1.2648552168552958, 'solver': 'sag'},
{'C': 1.2648552168552958, 'solver': 'saga'},
{'C': 2.559547922699533, 'solver': 'newton-cg'},
{'C': 2.559547922699533, 'solver': 'lbfgs'},
{'C': 2.559547922699533, 'solver': 'liblinear'},
{'C': 2.559547922699533, 'solver': 'sag'},
{'C': 2.559547922699533, 'solver': 'saga'},
{'C': 5.179474679231223, 'solver': 'newton-cg'},
{'C': 5.179474679231223, 'solver': 'lbfgs'},
{'C': 5.179474679231223, 'solver': 'liblinear'},
{'C': 5.179474679231223, 'solver': 'sag'},
{'C': 5.179474679231223, 'solver': 'saga'},
{'C': 10.481131341546874, 'solver': 'newton-cg'},
{'C': 10.481131341546874, 'solver': 'lbfgs'},
{'C': 10.481131341546874, 'solver': 'liblinear'},
{'C': 10.481131341546874, 'solver': 'sag'},
{'C': 10.481131341546874, 'solver': 'saga'},
{'C': 21.209508879201927, 'solver': 'newton-cg'},
{'C': 21.209508879201927, 'solver': 'lbfgs'},
{'C': 21.209508879201927, 'solver': 'liblinear'},
{'C': 21.209508879201927, 'solver': 'sag'},
{'C': 21.209508879201927, 'solver': 'saga'},
{'C': 42.91934260128778, 'solver': 'newton-cg'},
{'C': 42.91934260128778, 'solver': 'lbfgs'},
{'C': 42.91934260128778, 'solver': 'liblinear'},
{'C': 42.91934260128778, 'solver': 'sag'},
{'C': 42.91934260128778, 'solver': 'saga'},
{'C': 86.85113737513521, 'solver': 'newton-cg'},
{'C': 86.85113737513521, 'solver': 'lbfgs'},
{'C': 86.85113737513521, 'solver': 'liblinear'},
{'C': 86.85113737513521, 'solver': 'sag'},
{'C': 86.85113737513521, 'solver': 'saga'},
{'C': 175.75106248547965, 'solver': 'newton-cg'},
{'C': 175.75106248547965, 'solver': 'lbfgs'},
{'C': 175.75106248547965, 'solver': 'liblinear'},
{'C': 175.75106248547965, 'solver': 'sag'},
{'C': 175.75106248547965, 'solver': 'saga'},
{'C': 355.64803062231357, 'solver': 'newton-cg'},
{'C': 355.64803062231357, 'solver': 'lbfgs'},
{'C': 355.64803062231357, 'solver': 'liblinear'},
{'C': 355.64803062231357, 'solver': 'sag'},
{'C': 355.64803062231357, 'solver': 'saga'},
{'C': 719.6856730011528, 'solver': 'newton-cg'},
{'C': 719.6856730011528, 'solver': 'lbfgs'},
{'C': 719.6856730011528, 'solver': 'liblinear'},
{'C': 719.6856730011528, 'solver': 'sag'},
{'C': 719.6856730011528, 'solver': 'saga'},
{'C': 1456.3484775012444, 'solver': 'newton-cg'},
{'C': 1456.3484775012444, 'solver': 'lbfgs'},
{'C': 1456.3484775012444, 'solver': 'liblinear'},
{'C': 1456.3484775012444, 'solver': 'sag'},
{'C': 1456.3484775012444, 'solver': 'saga'},
{'C': 2947.0517025518097, 'solver': 'newton-cg'},
{'C': 2947.0517025518097, 'solver': 'lbfgs'},
{'C': 2947.0517025518097, 'solver': 'liblinear'},
{'C': 2947.0517025518097, 'solver': 'sag'},
{'C': 2947.0517025518097, 'solver': 'saga'},
{'C': 5963.623316594637, 'solver': 'newton-cg'},
{'C': 5963.623316594637, 'solver': 'lbfgs'},
{'C': 5963.623316594637, 'solver': 'liblinear'},
{'C': 5963.623316594637, 'solver': 'sag'},
{'C': 5963.623316594637, 'solver': 'saga'},
{'C': 12067.926406393313, 'solver': 'newton-cg'},
{'C': 12067.926406393313, 'solver': 'lbfgs'},
{'C': 12067.926406393313, 'solver': 'liblinear'},
{'C': 12067.926406393313, 'solver': 'sag'},
{'C': 12067.926406393313, 'solver': 'saga'},
{'C': 24420.53094548655, 'solver': 'newton-cg'},
{'C': 24420.53094548655, 'solver': 'lbfgs'},
{'C': 24420.53094548655, 'solver': 'liblinear'},
{'C': 24420.53094548655, 'solver': 'sag'},
{'C': 24420.53094548655, 'solver': 'saga'},
{'C': 49417.13361323838, 'solver': 'newton-cg'},
{'C': 49417.13361323838, 'solver': 'lbfgs'},
{'C': 49417.13361323838, 'solver': 'liblinear'},
{'C': 49417.13361323838, 'solver': 'sag'},
{'C': 49417.13361323838, 'solver': 'saga'},
{'C': 100000.0, 'solver': 'newton-cg'},
{'C': 100000.0, 'solver': 'lbfgs'},
{'C': 100000.0, 'solver': 'liblinear'},
{'C': 100000.0, 'solver': 'sag'},
{'C': 100000.0, 'solver': 'saga'}]
best_model.cv_results_['mean_test_score']
array([0.6078125, 0.7921875, 0.7921875, 0.6078125, 0.6078125, 0.6078125,
0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125,
0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875,
0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125,
0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125,
0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125,
0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125,
0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875,
0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125,
0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125,
0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125,
0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125,
0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875,
0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125,
0.6078125, 0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125,
0.6078125, 0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125,
0.6078125, 0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125,
0.7921875, 0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.79375 ,
0.6078125, 0.6078125, 0.6078125, 0.6078125, 0.79375 , 0.6078125,
0.6078125, 0.6078125, 0.6078125, 0.79375 , 0.6078125, 0.6078125,
0.6078125, 0.6078125, 0.79375 , 0.6078125, 0.6078125, 0.653125 ,
0.653125 , 0.79375 , 0.653125 , 0.653125 , 0.709375 , 0.709375 ,
0.790625 , 0.709375 , 0.709375 , 0.765625 , 0.765625 , 0.7890625,
0.765625 , 0.765625 , 0.7859375, 0.7859375, 0.7890625, 0.7859375,
0.7859375, 0.7859375, 0.7859375, 0.790625 , 0.7859375, 0.7859375,
0.7875 , 0.7875 , 0.7890625, 0.7875 , 0.7875 , 0.790625 ,
0.790625 , 0.7875 , 0.790625 , 0.790625 , 0.7921875, 0.7921875,
0.7875 , 0.7921875, 0.7921875, 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 , 0.7875 ,
0.7875 , 0.7875 , 0.7875 , 0.7875 ])
from sklearn.model_selection import GridSearchCV, train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 0.8, random_state = 1)
y_train = y_train.ravel()
y_test = y_test.ravel()
param_grid = {'C': np.logspace(-10, 5, 50), 'penalty': ['l1', 'l2']}
best_model = GridSearchCV(LogisticRegression(solver = 'liblinear', random_state = 0), param_grid, cv = 5, n_jobs = -1,verbose = 0, return_train_score = False )
best_model.fit(X_train, y_train)
print(best_model.best_estimator_)
print("Accuracy:", accuracy_score(y_test, best_model.predict(X_test)))
LogisticRegression(C=0.07543120063354607, penalty='l1', random_state=0,
solver='liblinear')
Accuracy: 0.7625
Сделайте выводы о влиянии выбора гиперпараметров на качество обучения линейной и SVC моделей. Также опишите, какие преобразования выборки/подбор каких гиперпараметров помогли добиться высокого качества на кросс-валидации в данной задаче.
В предыдущих заданиях мы убедились в мощности и гибкости моделей SVM. Теперь ответим на вопрос, насколько реально обучить модель SVM на выборках большого размера или с большим числом признаков.
Нужно провести два эксперимента. В первом перебирать размер выборки и для каждого запуска посчитать реальное время обучения модели. При этом делить выборку на обучение и тестирование не нужно. Также качество обученной модели в данном эксперименте не имеет значение. Размеры выборки предлагается перебирать в диапазоне range(1000, 10001, 1000) с использованием generate_data_with_balanced_classes(size=n).
Необходимо сравнить время обучения SVM с логистической регрессией. Для этого замеры повторите также для модели sklearn.linear_model.LogisticRegression. Время обучения одной модели замеряйте с помощью стандартной библиотеки time (пример в ячейке ниже).
Вы можете поставить эксперименты и с большими выборками, чем предлагается в задании (сгенерировать их),
тогда эффект должен быть виден еще сильнее.
Внимание! во время замеров времени работы, отключите сторонние процессы, занимающие CPU, иначе замеры времени работы окажутся некорректными. Помните, что времени работы в зависимости от числа данных и признаков должно меняться монотонно, без ступенчатых изменений (за исключением небольшого шума).
# Как замерять время
import time
time_start = time.time()
time.sleep(1) # Вместо этой команды - запуск замеряемого алгоритма
print("Время работы:", time.time() - time_start)
Время работы: 1.0013055801391602
def generate_data_with_balanced_classes(size=500, d=2, noise_scale=0.1):
X = np.random.normal(size=(size*2, d))
mask = X[:, 1] ** 2 > X[:, 0] - 0.1 + np.random.normal(scale=noise_scale)
y = np.ones(len(X))
y[mask] = 0
return X, y
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
svm = SVC(kernel='linear')
time_svm = []
time_logreg = []
for i in range(1000, 10001, 1000):
X, y = generate_data_with_balanced_classes(i, 2)
time_start = time.time()
svm.fit(X, y)
time_svm += [time.time() - time_start]
time_start = time.time()
logreg.fit(X, y)
time_logreg += [time.time() - time_start]
print("Время работы:\nSVM - {0},\nLogisticRegression - {1}".format(time_svm, time_logreg))
Время работы: SVM - [0.04839015007019043, 0.1858680248260498, 0.6095285415649414, 0.9180831909179688, 1.430161476135254, 2.310446262359619, 2.6454648971557617, 5.560093879699707, 5.36345100402832, 5.962019681930542], LogisticRegression - [0.005640983581542969, 0.008127450942993164, 0.012699365615844727, 0.01376199722290039, 0.017636537551879883, 0.022996187210083008, 0.02432727813720703, 0.10418343544006348, 0.043736934661865234, 0.030321359634399414]
plt.figure(figsize=(16,8))
plt.title('График времени работы в зависимости от числа объектов для SVM и логистической регрессии')
plt.ylabel('Время, сек')
plt.xlabel('Размер')
x_vals = [x for x in range(1000, 10001, 1000)]
plt.plot(x_vals, [time_svm[(x - 1000) // 1000] for x in x_vals],marker = '.', color='r')
plt.plot(x_vals, [time_logreg[(x - 1000) // 1000] for x in x_vals], marker = '.', color='g')
plt.grid(True)
plt.show()
Во втором эксперименте предлагается проделать то же самое, что и в первом эксперименте, только меняя размерность пространства признаков. Для этого можете воспользоваться функцией generate_data_with_balanced_classes(dim=d). Признаки предлагается перебирать по сетке $range(10, 1001, 100)$.
time_svm = []
time_logreg = []
for i in range(10, 1001, 100):
X, y = generate_data_with_balanced_classes(500, i)
time_start = time.time()
svm.fit(X, y)
time_svm += [time.time() - time_start]
time_start = time.time()
logreg.fit(X, y)
time_logreg += [time.time() - time_start]
plt.figure(figsize=(16,8))
plt.title('График времени работы в зависимости от размерности для SVM и логистической регрессии')
plt.ylabel('Время, сек')
plt.xlabel('Размер')
x_vals = [x for x in range(10, 1001, 100)]
plt.plot(x_vals, [time_svm[(x - 10) // 100] for x in x_vals],marker = '.', color='r')
plt.plot(x_vals, [time_logreg[(x - 10) // 100] for x in x_vals], marker = '.', color='g')
plt.grid(True)
plt.show()
Постройте графики времени работы в зависимости от числа объектов для SVM и логистической регрессии, сравните их и сделайте выводы.
Придумывая обоснование получившимся результатам попробуйте использовать вид решаемой задачи в SVM, который был дан вам на лекции.
>
Они обе по умолчанию используют l2 регуляризацию, но в svm - hinge функция потерь(max{0, 1 - ywx}), а не логистическая (log(1 + exp(1 - ywx))) Для svm нужно найти опорные объекты и их с каждым разом становиться больше. И max - не непрерывно дифференцируемая, что тоже отбирает время
Logistic Regression c помощью градиентного спуска апроксимирует данные, что намного быстрее выходит чем поиск опорных векторов
SVM также учитывает выбросы, что тоже замедляет алгоритм
Если уменьшение числа объектов сложная и зачастую невозможная задача, то для понижения числа признаков существует стандартное решение. В предыдущих заданиях Вы уже сталкивались с l1-регуляризацией, которая позволяла уменьшить число признаков в задаче линейной классификации/регрессии. Однако для большинства ML-алгоритмов такой способ уменьшения числа признаков неприменим.
Зато существует стандартное для всех алгоритмов понижение размерности входа. Данный алгоритм называется Principal Component Analysis (PCA, метод главных компонент). Он находит такое линейное пространство меньшей размерности $k$ ($k << d$, где d изначальная размерность входа), проекция на которое теряет меньше всего информации. Подробнее об этом можете почитать тут https://scikit-learn.org/stable/modules/decomposition.html#pca.
Эксперимент: Проекция в очень малое число координат сильно "упрощает" выборку, из-за чего качество решения задачи может в итоге упасть. В следующем эксперименте предлагается исследовать зависимость скорости работы метода и качества решения задачи при использовании понижения размерности. Требуется построить два графика:
При этом под полным циклом обучения подразумевается обучение PCA + обучение SVM. Данные для обучения: первые две координаты - луны, которые были в первом задании, а остальные координаты, случайные. Таким образом, без понижения размерности SVM с rbf ядром должен иметь точность близкую к 100\%. Чтобы лучше видеть эффект на графиках, можете менять размер генерируемой выборки. Для данных размеров $k$ рекомендуется перебирать от 10 до 500 (тогда будет видна требуемая закономерность).
P.S. Не забывайте делить выборку на обучение и валидацию в этом эксперименте (так как мы смотрим на качество, мы хотим считать его честно). При этом PCA как и любой другой алгоритм ML тоже нельзя учить на тесте. Общая схема применения PCA описана в ячейках ниже.
X_moons, y_moons = make_moons_cls(2000, 1000)
X_moons.shape
(2000, 1000)
plt.scatter(X_moons[:, 0], X_moons[:, 1], c=y_moons)
plt.xlabel('$x_0$')
plt.ylabel('$x_1$')
plt.show()
plt.scatter(X_moons[:, 0], X_moons[:, 2], c=y_moons)
plt.xlabel('$x_0$')
plt.ylabel('$x_2$')
plt.show()
plt.scatter(X_moons[:, 2], X_moons[:, 3], c=y_moons)
plt.xlabel('$x_2$')
plt.ylabel('$x_3$')
plt.show()
Казалось бы понизить размерность в этой задаче достаточно легко, нужно просто выбрать только первые две координаты. Однако из-за нелинейности разделяющей поверхности, для PCA это задача нетривиальна. Такая же ситуация наблюдается и в большинстве прикладных задач. Теперь переходите к эксперименту :)
# Пример правильного обучения PCA с делением на train/test
pca_model = PCA(n_components=500)
X_moons_train, X_moons_test, y_moons_train, y_moons_test = train_test_split(
X_moons, y_moons, test_size=0.2) # делим выборку на трейн тест для оценки качества всего алгоритма
X_train_for_pca, X_train_for_svc, y_train_for_pca, y_train_for_svc = train_test_split(
X_moons_train, y_moons_train, test_size=0.5) # делим выборку на трейн тест для оценки качества всего алгоритма
print(X_moons.shape, y_moons.shape)
print(X_moons_train.shape, y_moons_train.shape)
print(X_train_for_pca.shape, y_train_for_pca.shape)
# Учим PCA
pca_model.fit(X_train_for_pca)
# Применяем PCA
X_moons_test_transformed = pca_model.transform(X_moons_test)
X_train_for_svc_transformed = pca_model.transform(X_train_for_svc)
print(X_train_for_svc_transformed.shape)
# Учим SVC (на другой выборке чтобы не переобучиться)
svc_on_transformed = SVC(kernel='rbf')
svc_on_transformed.fit(X_train_for_svc_transformed, y_train_for_svc)
preds = svc_on_transformed.predict(X_moons_test_transformed)
print('Accuracy after PCA:', accuracy_score(preds, y_moons_test))
(2000, 1000) (2000,) (1600, 1000) (1600,) (800, 1000) (800,) (800, 500) Accuracy after PCA: 0.5675
# Как замерять время
import time
time_svm_plus_pca = []
acc = []
for i in range(10, 501, 10):
pca_model = PCA(n_components=i)
time_start = time.time()
# Учим PCA
pca_model.fit(X_train_for_pca)
# Применяем PCA
X_moons_test_transformed = pca_model.transform(X_moons_test)
X_train_for_svc_transformed = pca_model.transform(X_train_for_svc)
# Учим SVC (на другой выборке чтобы не переобучиться)
svc_on_transformed = SVC(kernel='rbf')
svc_on_transformed.fit(X_train_for_svc_transformed, y_train_for_svc)
preds = svc_on_transformed.predict(X_moons_test_transformed)
time_svm_plus_pca += [time.time() - time_start]
acc += [accuracy_score(preds, y_moons_test)]
plt.figure(figsize=(16,8))
plt.title('График зависимости времени работы от числа признаков 𝑘')
plt.ylabel('Время, сек')
plt.xlabel('Размерность')
x_vals = [x for x in range(10, 501, 10)]
plt.plot(x_vals, [time_svm_plus_pca[(x - 10) // 10] for x in x_vals],marker = '.', color='r')
plt.grid(True)
plt.show()
plt.figure(figsize=(16,8))
plt.title('График зависимости качества решения (accuracy) задачи классификации от числа признаков 𝑘')
plt.ylabel('Accuracy')
plt.xlabel('Размерность')
x_vals = [x for x in range(10, 501, 10)]
plt.plot(x_vals, [acc[(x - 10) // 10] for x in x_vals], marker = '.', color='g')
plt.grid(True)
plt.show()
/usr/local/lib/python3.7/dist-packages/IPython/core/pylabtools.py:128: UserWarning: Glyph 119896 (\N{MATHEMATICAL ITALIC SMALL K}) missing from current font.
fig.canvas.print_figure(bytes_io, **kw)
svc_on_transformed = SVC(kernel='rbf')
svc_on_transformed.fit(X_moons_train, y_moons_train)
preds = svc_on_transformed.predict(X_moons_test)
print("Accuracy without RCA:", accuracy_score(preds, y_moons_test))
Accurace without RCA: 0.7175
Опишите в выводе как ведет себя качество решения задачи и время работы в зависимости от числа компонент в PCA.
Время работы очевидно увеличивается, так как находить пространство признаков, которое теряет меньшее количество информации, сложнее.
Качество увеличиваеться волнами: увеличивается так как большее количество признаков дает больше информации для классификации, но возможно модель ошибочно их отбирает на каком-то этапе.
Иногда в задаче классификации важно знать уверенность отнесения к тому или иному классу. В SVM за это отвечает параметр отступа (margin), который можно посчитать, используя model.decision_function(X). Именно отступ до разделяющей кривой пытается максимизироавть модель во время обучения. Однако по нему сложно утверждать с какой вероятностью объект относится к тому или иному классу.
Чтобы это понять давайте построим следующую кривую: 1) нормализуем отступы так, чтобы они лежали в диапазоне от 0 до 1;
2) разделим все объекты на бины по нормализованному оступу (например на [0, 0.1), [0.1, 0.2)...);
3) для каждого бина построим точку с координатой x - равной среднему значению нормализованного оступа внутри бина, и с координатой y - равной доле объектов класса 1 внутри бина.
Заметим теперь, что если бы нормализованный отступ приблизительно равнялся вероятности отнесения к классу, то бину [x, x + 0.1) должна была бы соответствовать точка с координатой Х принадлежащей [x, x + 0.1) и координатой Y в том же диапазоне. Таким образом, чем лучше скоры модели показывают вероятность отнесения к классу 1, тем больше калибровочная кривая похожа на прямую из точки (0, 0) в (1, 1).
Кроме того построим аналогично кривую для логистической регрессии, взяв вместо нормализованного отступа - вероятность отнесения к первому классу через predict_proba. Так как логистическая регрессия оптимизирует LogLoss и её выходом уже являются вероятности отнесения к классам, будем ожидать что её кривая хорошо ляжет на прямую из (0, 0) в (1, 1).
from sklearn.datasets import make_classification
from sklearn.calibration import calibration_curve
from sklearn.linear_model import LogisticRegression
# generate 2 class dataset
X, y = make_classification(n_samples=5000, n_classes=2, weights=[1,1], random_state=1)
# split into train/test sets
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2)
# fit a model
model = SVC()
model.fit(trainX, trainy)
# predict probabilities
probs = model.decision_function(testX)
# reliability diagram
fop, mpv = calibration_curve(testy, probs, n_bins=10, normalize=True)
# plot perfectly calibrated
# plot model reliability
plt.plot(mpv, fop, marker='.', label='SVC')
plt.xlabel('classifier score')
plt.ylabel('[label=1] ratio')
logistic_model = LogisticRegression()
logistic_model.fit(trainX, trainy)
logistic_probs = logistic_model.predict_proba(testX)[:, 1]
# reliability diagram
log_fop, log_mpv = calibration_curve(testy, logistic_probs, n_bins=10, normalize=True)
# plot perfectly calibrated
# plot model reliability
plt.plot(log_mpv, log_fop, marker='.', label='logistic')
plt.plot([0, 1], [0, 1], linestyle='--', label='ideal')
plt.legend()
plt.show()
Вот мы и увидели проблему, линия отступов не ложится на пунктирную линию, что означает что оступы не соотвествуют реальным вероятностям отнесения к тому или иному классу.
Но не всё пропало! Из отступов всё ещё можно получить вероятность отнесения к классу. Для этого существует такая процедура как калибровка вероятностей, при которой отступ для каждого объекта преобразовывается таким образом, чтобы соответствовать вероятности класса. После такого преобразования, полученное число становится интерпретируемой мерой уверенности модели.
В данном задании Вам прелагается обучить логистическую регрессию на отступах модели, которая по оступу (margin) предсказывала бы класс. Именно вероятности этой калибровочной модели и будут нашими верными оценками вероятности класса для объекта:
$p(y_i | x_i) = p(y_i | margin_i)$
Для этого Вам потребуется написать несложный класс CalibratingLogisticRegression. И проверить что новые предсказания дают правильную калибровочную кривую
class CalibratingLogisticRegression:
def fit(self, x, y):
assert len(x.shape) == 1 or x.shape[1] == 1
logistic_model = LogisticRegression()
logistic_model.fit(x, y)
self.logistic_model = logistic_model
def predict_proba(self, x):
assert len(x.shape) == 1 or x.shape[1] == 1
return self.logistic_model.predict_proba(x)[:,1]
Калибровочную модель и исходную модель нельзя учить на одних и тех же данных, чтобы избежать переобучения. (распределение отступов на обучении и тестовой выборке, скорее всего очень сильно отличается)
model = SVC()
N = len(trainX) // 2
model.fit(trainX[:N], trainy[:N])
margin = model.decision_function(trainX[N:])
# Учим калибровочную модель на второй половине данных
calibrating_model = CalibratingLogisticRegression()
calibrating_model.fit(margin[:,None], trainy[N:,None])
prob = calibrating_model.predict_proba(margin[:, None])
# Как это выглядит на обучении
margin_sorted, probs_sorted_by_margin = zip(*sorted(zip(margin, prob)))
plt.figure(figsize=(10, 10))
plt.plot(margin[:,None], trainy[N:, None], 'ro')
plt.plot(margin_sorted, probs_sorted_by_margin, 'b')
plt.show()
/usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py:993: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). y = column_or_1d(y, warn=True)
probs = calibrating_model.predict_proba(model.decision_function(testX)[:,None])
# reliability diagram
fop, mpv = calibration_curve(testy, probs, n_bins=10, normalize=True)
# plot perfectly calibrated
plt.plot([0, 1], [0, 1], linestyle='--')
# plot calibrated reliability
plt.plot(mpv, fop, marker='.')
plt.show()
Обратите внимание, что кривая для откалиброванных отступов должня также строиться для бинов и состоянть из n_bins точек. Если у Вас получилось кривая из трёх точек, Вы что-то сделали неправильно :)
Сделайте выводы о полученной модели. В каких задачах калибровка вероятностей могла бы быть полезной?
Калибровка модели необходима, если нам нужны приближенные к реальным вероятностные результаты, а не их приближения. Особенно ее применение важно в отношении сложных нелинейных алгоритмов, поскольку они представляют неточные прогнозов
Найдите мем про SVM лучше чем этот:
Важно: самый простой способ вставить картинку будет через Google Colab (даже если вы изначально делали не в нем). Нажмите на "+ Text", в появившейся ячейке сделайте прикрепление картинки (как на скринах). Тогда ваша картинка "зашифруется" и будет корректно отображаться при конвертации в html